<?php

namespace App\Models;


use App\Service\BangCache;
use App\Service\Option;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use function Ramsey\Uuid\v1;


// 测试
class Base extends BaseModel
{
    const CREATED_AT = 'ctime';
    const UPDATED_AT = 'mtime';

    protected $dateFormat = 'U';
    protected $guarded    = [];
    protected $query;

    // 模型读取的时间戳
    protected $casts = [
        //'created_at' => 'date:Y-m-d',           // 日期
        //'deleted_at' => 'datetime:Y-m-d h:i:s'    // 日期时间
        'ctime' => 'timestamp',              // 时间戳
        'mtime' => 'timestamp',              // 日期
    ];

    //
    protected $error;

    // 缓存
    protected $cacheFields = ['id']; // 缓存字段，用于搜索绑定的字段

    // 初始化
    public function __construct(array $data = [])
    {
        parent::__construct($data);

        // 开启sql日志
        DB::connection()->enableQueryLog();
        // 获取真是表名（在执行数据库操作的时候在来获取）
        // $this->initialize();
    }

    // 初始化(主要用于组件多表查询)
    protected function initialize()
    {
    }


    // 获取图片
    public function getPicAttribute($value)
    {
        return $this->replaceDomain($value, 'full');
    }

    // 保存图片
    public function setPicAttribute($value)
    {
        return $this->replaceDomain($value, 'old');
    }


    ##########################################################################################
    ## 数据库操作

    // 获取所有的
    public function getAll($where = [], $order = 'id ASC')
    {
        $lists = $this->newQuery()->where($where)->orderByRaw($order)->get()->toArray();
        $lists = array_column($lists, null, 'id');

        return $lists;
    }

    // 获取所有的 缓存
    public function getAllCache($bangData, $where = [], $order = 'id ASC')
    {
        // 绑定缓存必须是数组  如果不是数组，则直接查询
        if(!is_array($bangData)){
            return $this->getAll($where, $order);
        }


        // 绑定检测绑定的缓存字段 缓存字段必须设置在模型中的 $cacheFields 中
        $hasFields = $this->checkBangData($bangData);
        if($hasFields === false){
            return $this->return_false($this->getError());
        }

        // 获取缓存
        $args  = compact('where');
        $bang  = new BangCache();
        $cName = $bang->makeCacheKey($this->table, 'list', $args);   // 所有参数执行 key
        $lists = $bang->getCache($cName);
        if(!empty($lists)){
            return $lists;
        }

        // 查询
        $lists = $this->getAll($where, $order);
        // 保存缓存
        $bang->addCache($cName, $lists);
        // 保存为绑定
        $this->addBang($bangData, $cName, 'list');

        return $lists;
    }

    // 获取多条数据 不翻页
    public function getList($where = [], $number = 10, $order = "id DESC", $offset = 0)
    {
        $fields = $this->getTableField(true);
        $query  = $this->newQuery()->orderByRaw($order)->where($where)->select($fields);
        if($number){
            $lists = $query->offset($offset)->limit($number)->get()->toArray();
        } else{
            $lists = $query->get()->toArray();
        }

        return $lists;
    }

    // 获取多条数据 不翻页 ：缓存
    public function getListCache($bangData, $where = [], $number = 10, $order = "id DESC", $offset = 0)
    {
        // 绑定缓存必须是数组  如果不是数组，则直接查询
        if(!is_array($bangData)){
            return $this->getList($where, $number, $order, $offset);
        }


        // 绑定检测绑定的缓存字段 缓存字段必须设置在模型中的 $cacheFields 中
        $hasFields = $this->checkBangData($bangData);
        if($hasFields === false){
            return $this->return_false($this->getError());
        }

        // 获取缓存
        $args  = compact('where', 'number', 'order', 'offset');
        $bang  = new BangCache();
        $cName = $bang->makeCacheKey($this->table, 'list', $args);   // 所有参数执行 key
        $lists = $bang->getCache($cName);

        // 查询
        if(is_null($lists)){
            $lists = $this->getList($where, $number, $order, $offset);

            // 保存缓存
            $bang->addCache($cName, $lists);
            // 保存为绑定
            $this->addBang($bangData, $cName, 'list');
        }

        return $lists;
    }

    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getListInIds($ids, $fields = ['*'], $pk = 'id')
    {
        $lists = $this->newQuery()->select($fields)->whereIn($pk, $ids)->get()->toArray();
        $lists = array_column($lists, null, $pk);

        return $lists;
    }

    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作） 缓存
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getListInIdsCache($ids, $fields = ['*'], $pk = 'id')
    {
        $lists = [];
        foreach($ids as $id){
            // 获取缓存
            /*$args      = compact('id', 'fields', 'pk');
            $cacheName = $this->cacheKey('getListInIds', $args);
            $cacheData = $this->cacheClass()->getCache($cacheName);
            if(empty($cacheData)){
                // 查询
                $info = $this->where($pk, $id)->select($fields)->first();
                $info = is_object($info) ? $info->toArray() : $info;
                $this->addCacheShow($info, $cacheName, $info);
                $cacheData = $info;
            }
            if($cacheData){
                $lists[$id] = $cacheData;
            }*/

            // 获取数据
            $info = $this->getOneByIdCache($id, $fields, $pk);
            if($info){
                $lists[$id] = $info;
            }
        }


        return $lists;
    }


    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getOneById($id, $fields = ['*'], $pk = 'id')
    {
        if(empty($id)){
            return [];
        }

        $info = $this->newQuery()->where($pk, $id)->select($fields)->first();
        $info = is_object($info) ? $info->toArray() : $info;

        return $info;
    }


    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）  缓存
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getOneByIdCache($id, $fields = ['*'], $pk = 'id')
    {
        if(empty($id)){
            return [];
        }


        $getOneById = true;
        // 如果缓存 则查询缓存
        $args  = compact('id', 'fields', 'pk', 'getOneById');
        $bang  = new BangCache();
        $cName = $bang->makeCacheKey($this->table, 'show', $args);   // 所有参数执行 key
        $info  = $bang->getCache($cName);

        // 查询
        if(empty($info)){
            $info = $this->getOneById($id, $fields, $pk);

            // 保存缓存
            $bang->addCache($cName, $info);
            if($info){
                // 添加绑定
                $bangData = ['id' => $info['id']];
                /*if (empty($bangData)) {
                    $bangData = $info ? ['id' => $info['id']] : [];
                }*/

                $tags = $bang->makeBang($this->table, $bangData, 'show');
                foreach($tags as $_tag){
                    $bang->addTag($_tag, $cName);
                }
            }
        }


        return $info;
    }

    // 获取多条数据+翻页
    public function listPage($where = [], $page = 1, $rows = 10, $order = "id DESC")
    {
        // 字段
        $fields = $this->getTableField(true);
        $query  = $this->newQuery()->select($fields)->forPage($page, $rows)->orderByRaw($order);

        // 查询  ->recycle()  ->orderby($order)
        if(empty($where)){
            $count = $this->count();
        } else{
            if(is_array($where)){
                $query = $query->where($where);
                $count = $this->where($where)->count();
            } elseif(is_string($where)){
                $query = $query->whereRaw($where);
                $count = $this->whereRaw($where)->count();
            }
        }

        // 查找过滤
        $is_next = 0;
        $lists   = $query->get()->toArray();
        // 查看是否需要翻页
        if($page * $rows < $count){
            $is_next = 1;
        }

        $result = ['total' => $count, 'page' => $page, 'rows' => $rows, 'is_next' => $is_next, 'list' => $lists,];


        return $result;
    }

    // 获取多条数据+翻页 ：缓存
    public function listPageCache($bangData, $where = [], $page = 1, $rows = 10, $order = "id DESC")
    {
        // 绑定缓存必须是数组  如果不是数组，则直接查询
        if(!is_array($bangData)){
            return $this->listPage($where, $page, $rows, $order);
        }

        // 绑定检测绑定的缓存字段 缓存字段必须设置在模型中的 $cacheFields 中
        $hasFields = $this->checkBangData($bangData);
        if($hasFields === false){
            return $this->return_false($this->getError());
        }

        // 获取缓存
        $args   = compact('where', 'page', 'rows', 'order');
        $bang   = new BangCache();
        $cName  = $bang->makeCacheKey($this->table, 'lists', $args);   // 所有参数执行 key
        $result = $bang->getCache($cName);

        // 执行查询
        if(empty($result)){
            $result = $this->listPage($where, $page, $rows, $order);
            // 保存缓存
            $bang->addCache($cName, $result);
            // 添加绑定
            $this->addBang($bangData, $cName, 'list');
        }

        return $result;
    }

    // 查询一个
    public function oneFind($where, $order = "id DESC")
    {
        $info = $this->newQuery()->where($where)->orderByRaw($order)->first(); // ->recycle()

        return $info;
    }

    // 查询一个：缓存
    public function oneFindCache($bangData, $where, $order = "id DESC")
    {
        // 绑定缓存必须是数组  如果不是数组，则直接查询
        if(!is_array($bangData)){
            return $this->oneFind($where, $order); // ->recycle()
        }

        // 绑定检测绑定的缓存字段 缓存字段必须设置在模型中的 $cacheFields 中
        $hasFields = $this->checkBangData($bangData);
        if($hasFields === false){
            return $this->return_false($this->getError());
        }

        // 获取缓存
        $args  = compact('where', 'order');
        $bang  = new BangCache();
        $cName = $bang->makeCacheKey($this->table, 'show', $args);   // 所有参数执行 key
        $info  = $bang->getCache($cName);
        if(!empty($info)){
            return $info;
        }

        // 执行查询
        $info = $this->oneFind($where, $order); // ->recycle()
        if(empty($info)){
            return $this->return_false('数据不存在！！');
        }

        // 保存为缓存
        $bang->addCache($cName, $info);
        // 添加绑定
        //$bangData = $bangData ?: ['id' => $info['id']];
        $bangData['id'] = $info['id'];
        $this->addBang($bangData, $cName, 'show');


        return $info;
    }


    // 添加
    public function oneAdd($data = [])
    {
        $this->error = '';

        // 模型 和 查询
        //$data = $this->filterNull($data);
        $this->fill($data);
        $result = $this->save();

        //$result = $model->create($data);  // create方法需要制定$fillable 所以这和上面操作是一样的
        // 或者
        //$data   = $model->fieldArray($data);
        //$result = $model->save($data);

        // 缓存操作
        $this->deleteCacheShow($data);

        return $this->id;
    }

    // 修改
    public function oneEdit($where, $data)
    {
        $info = $this->newQuery()->where($where)->first();  // ->recycle()
        if(empty($info)){
            return $this->return_false('你要修改的数据不存在！！');
        }

        // 保存
        //$data = $this->filterNull($data);
        //$info->fill($data);
        //$result = $info->save();

        // 保存
        $result = $this->newQuery()->where($where)->update($data);

        // 或者
        //$data   = $model->fieldArray($data);
        //$result = $model->where($where)->update($data);

        // 缓存操作
        $this->deleteCacheShow($info, $data);

        return $result;
    }

    // 删除
    public function oneDelete($where)
    {
        // ->newQuery()  // 删除以前查询的where
        $info = $this->newQuery()->where($where)->first();  //->recycle()
        if(empty($info)){
            return $this->return_false('data_delete_has_not_exist');
            //return $this->return_false('你要删除的数据不存在！！');
        }

        // 缓存操作
        $this->deleteCacheShow($info);

        return $info->delete();
    }

    // 执行软删除
    public function oneSoftDelete($where, $field = 'verify')
    {
        $info = $this->newQuery()->where($where)->first();  //->recycle()
        if(empty($info)){
            //return $this->return_false('你要删除的数据不存在！！');
            return $this->return_false('data_delete_has_not_exist');
        }

        $this->where('id', $info['id'])->update([$field => -1]);

        // 缓存操作
        $this->deleteCacheShow($info);

        return true;
    }

    // 删除
    public function allDelete($ids)
    {
        $list = $this->newQuery()->whereIn('id', $ids)->first();  //->recycle()
        if(empty($list)){
            //return $this->return_false('你要删除的数据不存在！！');
            return $this->return_false('data_delete_has_not_exist');
        }

        // 缓存操作
        $this->deleteCacheList($list);

        return $list->delete();
    }

    // join操作（暂未完成）
    public function joinList($where, $page, $field, $rows, $orderBy)
    {
        $query = $this->newQuery()->from('article model')->where($where)
                      ->join('article_topic as topic', 'topic.article_id', '=', 'article.id')
                      ->join('article_cate as cate', 'cate.article_id', '=', 'article.id');

        if($page){
            // 除了作家以外，都要排除最新三条数据（作家没有顶部置顶三条数据）
            $count    = $query->count();
            $articles = $query->select($field)->orderByRaw($orderBy)->forPage($page, $rows)->get()->toArray();
        } else{
            $articles = $query->orderBy('article.id', 'desc')->get()->toArray();
        }

        // 返回
        return $count;
    }

    // 分组返回数据（暂未完成）
    public function onGroupList($number = 1, $orderBy = "a.ctime DESC")
    {
        $table  = DB::getTablePrefix().'a';
        $count  = $number + 1;
        $_table = $this->getTable();
        // select count(*) from my8_bracelet_information where uid=a.uid and created_at < a.created_at having Count(*) < 2
        $subSql = $this->selectRaw('count(*)')->whereIn('author_id', $authorIds)->whereIn('a.author_id', $authorIds)
                       ->whereRaw('author_id=la_a.author_id and ctime < la_a.ctime')->havingRaw("count(*) < $count");
        // select a.* from my8_bracelet_information a where exists ($subSql)  order by a.created_at DESC;
        // ->from($articleDb->getTable().' as a')
        $articles = $this->from($_table." as a")->select('a.*')->mergeBindings($subSql->getQuery())
                         ->whereIn('a.author_id', $authorIds)
                         ->whereRaw("exists (".$subSql->toSql().")")->orderByRaw($orderBy)->get()->toArray();

        return $articles;
    }

    ##########################################################################################
    ## 字段

    // 获取数据表所有的字段
    public function getTableField($simple = false)
    {
        $table    = $this->getTable();
        $tableKey = $table.'_fields';
        $columns  = Cache::store('field')->get($tableKey);
        // 如果没有字段信息 重新在数据库中查询
        if(empty($columns)){
            //DB::enableQueryLog();

            //$fields  = Schema::getColumnListing($table); // 只有字段名称
            $dbName    = DB::getDatabaseName();
            $fulltable = DB::getTablePrefix().$table;
            $field_str = "COLUMN_NAME field,DATA_TYPE `itype`, CHARACTER_MAXIMUM_LENGTH  `length`, ";
            $field_str .= " IS_NULLABLE is_null, COLUMN_DEFAULT `default`, COLUMN_COMMENT comment";
            $where_sql = " WHERE table_name= ? AND  TABLE_SCHEMA=? ";
            $sql       = 'SELECT '.$field_str.' FROM information_schema.columns '.$where_sql;
            $result    = DB::select($sql, [$fulltable, $dbName]);
            $columns   = json_decode(json_encode($result), true);
            $columns   = array_column($columns, null, 'column_name');

            unset($columns['is_recycle']);  // 删除字段 is_recycle
            Cache::store('field')->set($tableKey, $columns);

            // 数据库语句
            //$sqls = DB::getQueryLog();
            //var_dump($sqls);
        }

        // 值需要字段就好
        if($simple){
            $columns = array_column($columns, 'field');
        }

        return $columns;
    }


    //获取全表名
    public function fullTable()
    {
        $table = $this->getTable();
        $table = DB::getTablePrefix().$table;

        return $table;
    }

    // 制作表单验证
    public function fieldRule()
    {
        // 判断是否有缓存
        $table    = $this->getTable();
        $tableKey = $table.'_rules';
        $rules    = Cache::store('fieldRule')->get($tableKey);
        if(!empty($rules)){
            return $rules;
        }

        $rules  = [];
        $fields = $this->getTableField();
        foreach($fields as $_item){
            // 参数
            $_field   = $_item['field'];
            $_itype   = $_item['itype'];
            $_length  = $_item['length'];
            $_default = $_item['default'];
            $_rule    = [];

            // 判断
            if($_field == 'id'){
                continue;
            }

            // 制作验证规则
            if(strpos($_itype, 'int') !== false){
                $_rule[] = 'integer';
            }

            if($_itype == 'tinyint'){
                $_rule[] = 'max:32';
            } elseif($_itype == 'varchar' && $_length){
                $_rule[] = 'max:'.$_length;
            } elseif($_itype == 'text'){
                $_rule[] = 'max:'.$_length;
            }

            // 整合验证规则（如果不存在，则该字段传值则不能为空）
            // $_default
            if(!empty($_rule) && ($_field != 'ip_address')){
                array_unshift($_rule, 'sometimes', 'required');
                $rules[$_field] = $_rule;
            }
        }

        // 缓存
        Cache::store('fieldRule')->set($tableKey, $rules);

        return $rules;
    }

    // 获取自增长值
    public function getAutoInc()
    {
        $table     = $this->getTable();
        $dbName    = DB::getDatabaseName();
        $fulltable = DB::getTablePrefix().$table;
        $doSql     = "SELECT auto_increment FROM information_schema.tables  WHERE TABLE_NAME= ? AND TABLE_SCHEMA = ? ";
        $result    = DB::select($doSql, [$fulltable, $dbName]);

        return $result[0]->auto_increment;
    }

    // 获取需要查询的字段

    ##########################################################################################
    ## 缓存操作


    // 缓存用于绑定的缓存（如果没有缓存的时候，一般不能保存，所以这里用于绑定）
    //  已经不在使用，已改在各个操作查询中了
    public function bangData($data)
    {
        if(empty($data)){
            return true;
        }

        // 判断绑定的字段不存在与模型设置的表缓存字段中的
        // 绑定的字段
        $fields = array_keys($data);
        $res    = $this->checkBangField($fields);
        if($res === false){
            return $res;
        }

        return $data;
    }

    // 缓存用于绑定的缓存（如果没有缓存的时候，一般不能保存，所以这里用于绑定）
    public function checkBangData($data)
    {
        if(empty($data)){
            return true;
        }

        // 判断绑定的字段不存在与模型设置的表缓存字段中的
        // 绑定的字段
        $fields = array_keys($data);
        $res    = $this->checkBangField($fields);
        if($res === false){
            return $res;
        }

        return $data;
    }

    // 添加绑定
    public function addBang($bangData, $cFile, $self = 'list')
    {
        if(empty($cFile)){
            return;
        }

        $bang = new BangCache();
        $tags = $bang->makeBang($this->table, $bangData, $self);
        foreach($tags as $_tag){
            $bang->addTag($_tag, $cFile);
        }
    }

    // 删除详情缓存
    // 删除详情的时候，删除缓存字段值的绑定（所以操作删除缓存的时候，要先查询出详情）
    public function deleteCacheShow($info, $old = [])
    {
        $bang   = new BangCache();
        $table  = $this->table;
        $fields = $this->getCacheFields();
        // 删除绑定缓存
        /*if($this->bangData){
            $tags = $bang->makeBang($table, $this->bangData);
            $bang->deleteTag($tags);
        }*/

        // 删除绑定缓存
        $map = [];
        foreach($fields as $_field){
            if(isset($info[$_field])){
                $map[$_field] = $info[$_field];
            }
        }
        $tags = $bang->makeBang($table, $map);
        $bang->deleteTag($tags);


        // 删除绑定缓存
        if($old){
            $map = [];
            foreach($fields as $_field){
                if(isset($old[$_field])){
                    $map[$_field] = $old[$_field];
                }
            }
            if($map){
                $tags = $bang->makeBang($table, $map);
                $bang->deleteTag($tags);
            }
        }


        // 删除其他绑定缓存
        $tags = [$table, $table.'_list', $table.'_lists', $table.'_count', $table.'_join', $table.'_tree',];
        $bang->deleteTag($tags);

    }

    // 删除列表缓存
    // TODO 几乎很少用到，只有在批量操作时候才有用 或者 某个条件（如某个外键）的删除
    public function deleteCacheList($list)
    {
        $bang   = new BangCache();
        $table  = $this->table;
        $fields = $this->getCacheFields();
        // 删除绑定缓存
        /*if($this->bangData){
            $tags = $bang->makeBang($table, $this->bangData);
            $bang->deleteTag($tags);
        }*/

        // 删除绑定缓存
        foreach($list as $row){
            $map = [];
            foreach($fields as $_field){
                if(isset($row[$_field])){
                    $map[$_field] = $row[$_field];
                }
            }
            // 删除
            $tags = $bang->makeBang($table, $map);
            $bang->deleteTag($tags);
        }

        // 删除其他绑定缓存
        $tags = [$table, $table.'_list', $table.'_lists', $table.'_count', $table.'_join', $table.'_tree',];
        $bang->deleteTag($tags);
    }

    // 缓存字段
    protected function getCacheFields()
    {
        $fields = $this->cacheFields;
        if(empty($fields)){
            return ['id'];
        }

        if(is_string($fields)){
            $fields = explode(',', $fields);
        }
        if(!in_array('id', $fields)){
            $fields[] = 'id';
        }

        return $fields;
    }


    ##########################################################################################

    // 返回错误信息
    protected function return_false($msg = '操作失败！！')
    {
        $this->error = $msg;

        return false;
    }

    // 模型文件里面设置的验证规则（由每个模型文件改写）
    ## 特殊验证规则 +（最小长度，最小值）
    // 枚举：
    // 必填：
    // 邮箱等：
    protected function rules()
    {
        return [];
    }

    // 替换域名（保存到数据库中和获取到页面显示改变数据中的域名地址）（用于修改器与获取器）
    protected function replaceDomain($value, $to = 'full')
    {
        if(empty($value)){
            return $value;
        }

        // 替换
        $option    = new Option();
        $oldDomain = $option->domain('old');
        $nowDomain = $option->domain('oss');

        // full：展示
        if($to == 'full'){
            $value = str_replace($oldDomain, $nowDomain, $value);
        } else{
            // old：保存 保存为最原始的域名
            $value = str_replace($nowDomain, $oldDomain, $value);
        }

        return $value;
    }

    // 获取错误信息
    public function getError()
    {
        return $this->error;
    }

    // 过滤不存在表中的字段
    public function filterField($data, $delNull = false)
    {
        $fields = $this->getTableField(true);
        foreach($data as $key => $val){
            if(!in_array($key, $fields)){
                unset($data[$key]);
                continue;
            }
            if($delNull && is_null($data[$key])){
                unset($data[$key]);
            }
        }

        return $data;
    }

    // 获取执行的sql
    public function getSql()
    {
        return DB::getQueryLog();
    }

    // 获取执行的最后一条sql
    public function getLastSql()
    {
        $sqls = DB::getQueryLog();

        return $sqls[count($sqls) - 1] ?? '';
    }

    // 释放sql内存（尤其是命令行模式下，语句都存在内存中，容易溢出）
    public function flushQueryLog()
    {
        DB::flushQueryLog();
    }

    // 检查绑定的字段是否设置了缓存字段
    public function checkBangField($fields)
    {
        // 判断缓存字段是否存在
        $cFields = $this->getCacheFields();
        if(empty($cFields)){
            return $this->return_false('请设置缓存字段！');
        }
        if(is_string($cFields)){
            $cFields = explode(',', $cFields);
        }
        // 不存在的字段
        $noFields = '';
        $comm     = '';
        foreach($fields as $_field){
            if(!in_array($_field, $cFields)){
                $noFields .= $comm.$_field;
                $comm     = '，';
            }
        }
        if($noFields){
            return $this->return_false('字段：'.$noFields.' 未设置缓存字段！');
        }

        return true;
    }

}


